import { Graph, Node, Edge } from '@antv/x6'
import { Snapline } from '@antv/x6-plugin-snapline'
import { register } from '@antv/x6-vue-shape'
import FiledLineageNode from './FiledLineageNode.vue'
import TableLineageNode from './TableLineageNode.vue'
import { cloneDeep, compact, isArray, isEqual, isString } from 'lodash'
import {
  graphConfigOptions,
  generateNodesTemplate,
  generateEdgesTemplate,
  generateEdgesAttributes,
} from './util'
import { arrayToObjectByKey } from '@/util'
import { SearchResult, NodeInfo } from './interface'
import { updateNodePortsStyle } from './node-port'

const dagre = require('dagre')

// 注册 Vue 组件 节点
register({
  shape: 'field-data-lineage-node',
  component: FiledLineageNode,
})
register({
  shape: 'table-data-lineage-node',
  component: TableLineageNode,
})

/**
 * 基于 X6 Graph LineageGraph
 */
export default class LineageGraph {
  private readonly _graph: any = null // X6 画布实例
  private readonly _nodeType: 'TABLE' | 'FIELD' = 'FIELD' // 当前画布节点类型
  private _graphData: { nodes: any[]; edges: any[] } = { nodes: [], edges: [] } // 画布数据
  private _initGraphDataCache: { nodes: any[]; edges: any[] } = {
    nodes: [],
    edges: [],
  } // 记录初始化画布信息

  private _selectedNode: NodeInfo | null = null // selected 节点信息
  private _onSelectedHighlightLinkNodes: any[] = [] // selected 高亮链路上的节点
  private _onSelectedHighlightLinkEdges: any[] = [] // selected 高亮链路上的边
  private _hoverNode: NodeInfo | null = null // hover 节点信息
  private _onHoverHighlightLinkNodes: any[] = [] // hover 高亮链路上的节点
  private _onHoverHighlightLinkEdges: any[] = [] // hover 高亮链路上的边
  private _selectedEdgeId: string | null = null // selected 边id

  public dagreLayout: any = null // 层次布局实例

  public get graph() {
    return this._graph
  }

  /**
   * 构造器
   * @param options
   */
  constructor(options: any) {
    const { width, height, container, nodeType, plugins } = options
    this._graph = this.initGraph(container, width, height)
    this._nodeType = nodeType
    this.clearGraphData()
    this.usePlugins(plugins)
    this.initDagreLayout()
  }

  /**
   * 初始化 dagre layout
   * @private
   */
  private initDagreLayout() {
    this.dagreLayout = new dagre.graphlib.Graph({
      multigraph: true,
      compound: true,
    })
    this.dagreLayout.setGraph({
      rankdir: 'LR',
      // align: 'UL', // UL, UR, DL, or DR
      ranksep: 120,
      nodesep: 30,
      edgesep: 1,
      marginx: 40,
      marginy: 20,
      // ranker: 'tight-tree',  // network-simplex  tight-tree or longest-path
    })
    this.dagreLayout.setDefaultEdgeLabel(() => {
      return {}
    })
  }

  /**
   * 重置 dagre layout
   * @private
   */
  private resetDagreLayout() {
    if (this.dagreLayout) {
      const nodes: any = this.dagreLayout.nodes()
      const edges: any = this.dagreLayout.edges()
      nodes.forEach((id: any) => {
        this.dagreLayout.removeNode(id)
      })
      edges.forEach(({ v, w }: any) => {
        this.dagreLayout.removeEdge(v, w)
      })
    }
  }

  /**
   * dagre layout 添加节点和边
   * @param nodes
   * @param edges
   * @private
   */
  private dagreLayoutSetCell(nodes: any[], edges: any[]) {
    nodes.forEach((node: any) => {
      this.dagreLayout.setNode(node.id, node)
    })
    edges.forEach((edge: any) => {
      this.dagreLayout.setEdge(edge.source.cell, edge.target.cell, {}, edge.id)
    })
  }

  /**
   * 执行 dagre layout
   * @private
   */
  private executeDagreLayout() {
    dagre.layout(this.dagreLayout)
    const self: any = this
    const mt = 20
    const vt = 20
    this.dagreLayout.nodes().forEach((n: any) => {
      const r = self.dagreLayout.node(n)
      if (r) {
        r.x = r.x - r.width / 2 + vt
        r.y = r.y - r.height / 2 + mt
      }
    })
    return Object.values(this.dagreLayout._nodes)
  }

  /**
   * 销毁 X6 graph
   */
  public dispose() {
    this._graphData.nodes = []
    this._graphData.edges = []
    this._initGraphDataCache.edges = []
    this._initGraphDataCache.nodes = []
    this.dagreLayout = null
    this._selectedNode = null
    this._graph.dispose()
  }

  /**
   * 实例化 X6 graph
   * @param container 画布 dom
   * @param width 指定画布宽
   * @param height 指定画布高
   */
  public initGraph(container: any, width: number, height: number) {
    return new Graph({
      ...graphConfigOptions,
      container,
      width,
      height,
    })
  }

  /**
   * use x6 plugin
   * @param plugins 应用 x6 plugins
   */
  public usePlugins(plugins: string[]) {
    if (plugins.includes('snapline')) {
      this.graph.use(
        new Snapline({
          enabled: true,
        })
      )
    }
  }

  /**
   * 清除画布
   */
  public clearGraphData() {
    this.graph.clearCells()
    this.resetDagreLayout()
    this._graphData.nodes = []
    this._graphData.edges = []
  }

  /**
   * 平移画布
   * @param offset
   * @param byCurrent
   * @private
   */
  private translateGraph(
    offset: { offsetX: number; offsetY: number },
    byCurrent: boolean = true
  ) {
    let { offsetX, offsetY } = offset
    if (byCurrent) {
      const { tx, ty } = this._graph.translate()
      offsetX += tx
      offsetY += ty
    }
    this._graph.translate(offsetX, offsetY)
  }

  /**
   * 获取所有节点(指定字段)信息
   * @param key 指定的字段
   */
  public getGraphNodesInfo(
    key: string | string[] = [
      'databaseName',
      'tableName',
      'tableFieldDetailList',
      'entranceNumber',
      'exitNumber',
      'haveEntrance',
      'haveExit',
    ]
  ) {
    const allNodes: any[] = this._graph.getNodes()
    // 参数归一化
    const getKeys: string[] = typeof key === 'string' ? [key] : key
    return (
      allNodes?.map((node: any) => {
        const info: any = {}
        getKeys.forEach((k: string) => {
          info[k] = Object.keys(node.data).includes(k)
            ? node.data[k]
            : undefined
        })
        return info
      }) || []
    )
  }

  /**
   * 获取所有节点(指定字段)信息
   * @param key 指定的字段
   */
  public getGraphEdgesInfo(
    key: string | string[] = ['source', 'target', 'lineColor', 'lineType']
  ) {
    const allEdges: any[] = this._graph.getEdges()
    // 参数归一化
    const getKeys: string[] = typeof key === 'string' ? [key] : key
    return (
      allEdges?.map((edge: any) => {
        const info: any = {}
        getKeys.forEach((k: string) => {
          info[k] = Object.keys(edge.data).includes(k)
            ? edge.data[k]
            : undefined
        })
        return info
      }) || []
    )
  }

  /**
   * 过滤画布已存在的重复节点和边
   * @param nodes
   * @param edges
   */
  public filterExistedNodesAndEdge(
    nodes: any[],
    edges?: any[]
  ): { filterExistedNodes: any[]; filterExistedEdges?: any[] } {
    let filterExistedNodes: any[] = []
    let filterExistedEdges: any[] = []
    if (nodes.length > 0) {
      filterExistedNodes = nodes.filter(
        (node: any) => node && !this._graph.hasCell(node.id)
      )
    }
    if (edges && edges.length > 0) {
      filterExistedEdges = edges?.filter(
        (edge: any) => edge && !this._graph.hasCell(edge.id)
      )
    }
    return {
      filterExistedNodes,
      filterExistedEdges,
    }
  }

  /**
   * 恢复画布到初始状态
   */
  public recoverRenderGraph() {
    this.clearGraphData()
    this.resetDagreLayout()
    // 不使用深copy 在后续的 布局操作（executeDagreLayout） 会污染 this._initGraphDataCache 中的节点位置信息
    const { nodes = [], edges = [] } = cloneDeep(this._initGraphDataCache)
    if (nodes.length > 0) {
      this._graphData.nodes.push(...nodes)
      this._graphData.edges.push(...edges)
      this.dagreLayoutSetCell(nodes, edges)
      this.renderNodesWithPosition(nodes)
      this.renderEdges(edges)
    }
    this._graph.scaleContentToFit({
      padding: 20,
    })
    this._graph.centerContent({
      padding: 20,
    })
  }

  /**
   * 添加节点/边
   * @param nodes
   * @param edges
   */
  public addNodesAndEdges(nodes: any[], edges: any[]) {
    let addNodes: any[] = []
    let addEdges: any[] = []
    const { filterExistedNodes = [] } = this.filterExistedNodesAndEdge(nodes)
    if (filterExistedNodes.length > 0) {
      addNodes = generateNodesTemplate(nodes, this._nodeType)
      this._graphData.nodes.push(...addNodes)
    }
    if (edges.length > 0) {
      addEdges = generateEdgesTemplate(edges, this._nodeType)
      this._graphData.edges.push(...addEdges)
    }
    return { addNodes, addEdges }
  }

  /**
   * 渲染节点&边
   * @param nodes
   * @param edges
   * @param init
   * @param baseNode
   */
  public renderGraph(
    nodes: any[],
    edges: any[],
    init: boolean = false,
    baseNode?: any
  ) {
    const { addNodes, addEdges } = this.addNodesAndEdges(nodes, edges)
    const offset: any = {
      offsetX: 0,
      offsetY: 0,
    }
    let layoutNodes: any = []
    if (addNodes.length > 0) {
      this.dagreLayoutSetCell(addNodes, addEdges)
      layoutNodes = this.executeDagreLayout()
      if (baseNode) {
        const nodeId: string = `${baseNode.databaseName}-${baseNode.tableName}`
        const currentNodePosition: any = this._graph
          .getCellById(nodeId)
          .position()
        const findCurrentNodeId: any = layoutNodes.find(
          (node: any) => node.id === nodeId
        )
        offset.offsetX = currentNodePosition.x - findCurrentNodeId.x
        offset.offsetY = currentNodePosition.y - findCurrentNodeId.y
      }
      this.renderNodesWithPosition(layoutNodes, init)
    }
    if (init) {
      this._initGraphDataCache.nodes = cloneDeep(layoutNodes)
      this._initGraphDataCache.edges = cloneDeep(addEdges)
      this._graph.scaleContentToFit({
        padding: 20,
      })
      this._graph.centerContent({
        padding: 20,
      })
    }
    // 有新增边
    this.renderEdges(addEdges)
    // 移动偏移量
    if (offset.offsetX !== 0 || offset.offsetY !== 0) {
      this.translateGraph(offset)
    }
  }

  /**
   * 渲染节点
   * @param nodes
   * @param init 初始状态渲染不需要检测重复节点
   */
  public renderNodesWithPosition(nodes: any[], init: boolean = false) {
    if (init) {
      nodes.forEach((node: any) => {
        if (!node) {
          return
        }
        this._graph.addNode(node)
      })
    } else {
      nodes.forEach((node: any) => {
        if (!node) {
          return
        }
        // 已存在代表需要修改节点
        if (this._graph.hasCell(node.id)) {
          const currentNode: Node = this._graph.getCellById(node.id)
          const { x, y } = currentNode.position()
          if (x !== node.x || y !== node.y) {
            currentNode.position(node.x, node.y)
          }
          // 如果业务数据有更改
          if (!isEqual(node.data, currentNode.data)) {
            // 整体更新业务数据 data
            currentNode.setData(node.data, { overwrite: true })
            this.updateNodeBasedOnBusinessData(currentNode)
          }
        } else {
          this._graph.addNode(node)
        }
      })
    }
  }

  /**
   * 渲染边
   * @param edges
   */
  public renderEdges(edges: any[]) {
    if (edges.length > 0) {
      edges.forEach((edge: any) => {
        // 已存在代表需要修改边状态
        if (this._graph.hasCell(edge.id)) {
          const currentEdge: Edge = this._graph.getCellById(edge.id)
          // 整体更新业务数据 data
          currentEdge.updateData(edge.data)
          // 需要更新边的状态
          this.updateEdgeBasedOnBusinessData(currentEdge)
        } else {
          this._graph.addEdge(edge)
        }
      })
    }
  }

  /**
   * 基于业务数据 data 更新节点
   * @private
   */
  private updateNodeBasedOnBusinessData(
    node: string | Array<string> | Node | Array<Node>
  ) {
    const updateNodes: any[] = isArray(node) ? node : [node]
    updateNodes?.forEach((item: any) => {
      const currentNode: any = isString(item)
        ? this._graph.getCellById(item)
        : item
      updateNodePortsStyle(currentNode)
    })
  }

  /**
   * 基于业务数据 data 更新边
   * @private
   */
  private updateEdgeBasedOnBusinessData(
    edge: string | Array<string> | Edge | Array<Edge>
  ) {
    const updateEdges: any[] = isArray(edge) ? edge : [edge]
    updateEdges?.forEach((item: any) => {
      const currentEdge: any = isString(item)
        ? this._graph.getCellById(item)
        : item
      const { lineColor, lineType } = currentEdge.data
      const { edgeBaseColor, strokeDasharray } = generateEdgesAttributes({
        lineColor,
        lineType,
      })
      currentEdge.setAttrs({
        line: {
          stroke: edgeBaseColor,
          strokeDasharray,
        },
      })
      currentEdge.setPropByPath('states/baseColor', edgeBaseColor)
    })
  }

  /**
   * 根据节点 id / id & field 查询链路 ------------------- start ------------------
   */

  /**
   * 查询上/下游 一步的节点和边
   *  @param nodeId: 当前节点Id,
   *  @param type: 'UPSTREAM'-上游 | 'DOWNSTREAM'-下游,
   *  @param fieldName: 字段名称 - 无参数表示是表格级血缘,
   */
  public searchNeighborsNodesAndEdges(
    nodeId: string,
    type: 'UPSTREAM' | 'DOWNSTREAM',
    fieldName?: string
  ): SearchResult {
    const result: any = {
      linkNodes: [], // 查询的节点
      linkEdges: [], // 查询的边
    }
    const graph: any = this._graph
    const keyOfConnectCurrentNode: string =
      type === 'UPSTREAM' ? 'target' : 'source'
    const keyOfConnectSearchNode: string =
      type === 'UPSTREAM' ? 'source' : 'target'
    const portPrefix: string = type === 'UPSTREAM' ? 'in' : 'out'
    const portId: string = fieldName
      ? `${portPrefix}-${fieldName}`
      : `${portPrefix}-port`
    const currentNode: any = graph.getCellById(nodeId)
    // 是否有链接桩（是否有上下游节点）
    if (currentNode && !currentNode.hasPort(portId)) {
      return result
    }
    let currentNodeEdge: any =
      type === 'UPSTREAM'
        ? graph.getIncomingEdges(nodeId)
        : graph.getOutgoingEdges(nodeId)
    if (currentNodeEdge && currentNodeEdge.length > 0) {
      // 字段级血缘过滤一下
      if (fieldName) {
        currentNodeEdge = currentNodeEdge?.filter(
          (edge: any) =>
            edge.data[keyOfConnectCurrentNode].fieldName === fieldName
        )
      }
      currentNodeEdge?.forEach((item: any) => {
        const { id } = item.data
        const { databaseName, tableName } = item.data[keyOfConnectSearchNode]
        result.linkEdges.push(id)
        const currentEdgeSourceNode: any = graph.getCellById(
          `${databaseName}_${tableName}`
        )
        const linkNode: any = {
          id: currentEdgeSourceNode.id,
        }
        if (fieldName) {
          linkNode.fieldName = item.data[keyOfConnectSearchNode].fieldName
        }
        result.linkNodes.push(linkNode)
      })
    }
    return result
  }

  /**
   * 查询字段级/表格级血缘关系（上游/下游）链路
   *  @param nodeId: 节点id,
   *  @param type: 'UPSTREAM'-上游 | 'DOWNSTREAM'-下游,
   *  @param fieldName: 字段名称 - 无参数表示是表格级血缘,
   */
  public searchLinksByTableAndField(
    nodeId: string,
    type: 'UPSTREAM' | 'DOWNSTREAM',
    fieldName?: string
  ) {
    const queue: any[] = [] // 查询队列
    const visited: any = {} // 是否查询过
    let linkNodeArray: any[] = [] // 所有 nodes
    let linkEdgeArray: any[] = [] // 所有 edges
    queue.push({
      id: nodeId,
      fieldName,
    })
    while (queue.length > 0) {
      const next = queue.shift()
      const visitedKey: string =
        this._nodeType === 'FIELD' ? `${next.id}_${next.fieldName}` : next.id
      if (visited[visitedKey]) {
        // eslint-disable-next-line no-continue
        continue
      }
      visited[visitedKey] = true
      const { linkEdges, linkNodes } = this.searchNeighborsNodesAndEdges(
        next.id,
        type,
        next.fieldName
      )
      queue.push(...linkNodes)
      linkEdgeArray = linkEdgeArray.concat(linkEdges)
      linkNodeArray = linkNodeArray.concat(linkNodes)
    }
    return {
      linkNodeArray,
      linkEdgeArray,
    }
  }

  /**
   * 查询字段级/表格级血缘关系（上游/下游/上下游）链路
   * @param nodeId
   * @param type
   * @param includeSelf
   * @param fieldName
   */
  public searchLinkNodesAndEdgesByNodeId(
    nodeId: string,
    type: 'UPSTREAM' | 'DOWNSTREAM' | 'ALL',
    includeSelf: boolean = false,
    fieldName?: string
  ) {
    const result: any = {
      linkNodes: [],
      linkEdges: [],
    }
    let upstream: any
    let downstream: any
    if (type === 'UPSTREAM' || type === 'ALL') {
      upstream = this.searchLinksByTableAndField(nodeId, 'UPSTREAM', fieldName)
    }
    if (type === 'DOWNSTREAM' || type === 'ALL') {
      downstream = this.searchLinksByTableAndField(
        nodeId,
        'DOWNSTREAM',
        fieldName
      )
    }
    result.linkNodes = [...upstream.linkNodeArray, ...downstream.linkNodeArray]
    result.linkEdges = [...upstream.linkEdgeArray, ...downstream.linkEdgeArray]

    if (includeSelf) {
      result.linkNodes.push({
        id: nodeId,
        fieldName,
      })
    }
    return result
  }
  /**
   * 根据节点 id / id & field 查询链路 ------------------- end ------------------
   */

  /**
   * ------------------ toggle select node start ------------------
   */

  /**
   * toggle 选择节点(字段级/表级)
   * @param nodeInfo { tableName: string, fieldName?: string } 表级无 fieldName
   */
  public toggleSelectedNode(nodeInfo: NodeInfo) {
    const { nodeId, fieldName } = nodeInfo
    // 字段级血缘
    if (this._nodeType === 'FIELD') {
      if (
        nodeId &&
        fieldName &&
        (this._selectedNode?.nodeId !== nodeId ||
          this._selectedNode?.fieldName !== fieldName)
      ) {
        this.clearOnSelectedHighlightLinkStates(true)
        const { linkNodes, linkEdges } = this.searchLinkNodesAndEdgesByNodeId(
          nodeId,
          'ALL',
          true,
          fieldName
        )
        this._selectedNode = nodeInfo
        this._onSelectedHighlightLinkNodes = linkNodes
        this._onSelectedHighlightLinkEdges = linkEdges
        const linkNodesMap: any = arrayToObjectByKey(
          linkNodes,
          'id',
          'fieldName'
        )
        const statesCallback: any = ({ node, historyStates }: any) => {
          const addFieldNames = linkNodesMap[node.id] || []
          return {
            ...historyStates,
            selected: nodeId === node.id,
            haveHighlightLink: linkNodes.length > 0,
            onSelectedHighlightLink: !!linkNodesMap[node.id],
            onSelectedHighlightLinkField: compact([
              ...addFieldNames,
              ...(historyStates.onSelectedHighlightLinkField || []),
            ]),
          }
        }
        this.updateNodesOnSelectedHighlightLinkStates(linkNodes, statesCallback)
        this.updateEdgesOnSelectedHighlightLinkStates(linkEdges, true)
      } else {
        this._selectedNode = null
        this.clearOnSelectedHighlightLinkStates(true)
        this._onSelectedHighlightLinkNodes = []
        this._onSelectedHighlightLinkEdges = []
      }
      // 表格级血缘
    } else if (this._nodeType === 'TABLE') {
      if (nodeId && this._selectedNode?.nodeId !== nodeId) {
        this.clearOnSelectedHighlightLinkStates(true)
        const { linkNodes, linkEdges } = this.searchLinkNodesAndEdgesByNodeId(
          nodeId,
          'ALL',
          true,
          fieldName
        )
        this._selectedNode = nodeInfo
        this._onSelectedHighlightLinkNodes = linkNodes
        this._onSelectedHighlightLinkEdges = linkEdges
        const linkNodesMap: any = arrayToObjectByKey(
          linkNodes,
          'id',
          'fieldName'
        )
        const statesCallback: any = ({ node, historyStates }: any) => {
          const addFieldNames = linkNodesMap[node.id] || []
          return {
            ...historyStates,
            selected: nodeId === node.id,
            haveHighlightLink: linkNodes.length > 0,
            onSelectedHighlightLink: !!linkNodesMap[node.id],
            onSelectedHighlightLinkField: compact([
              addFieldNames,
              ...(historyStates.onSelectedHighlightLinkField || []),
            ]),
          }
        }
        this.updateNodesOnSelectedHighlightLinkStates(linkNodes, statesCallback)
        this.updateEdgesOnSelectedHighlightLinkStates(linkEdges, true)
      } else {
        this._selectedNode = null
        this.clearOnSelectedHighlightLinkStates(true)
        this._onSelectedHighlightLinkNodes = []
        this._onSelectedHighlightLinkEdges = []
      }
    }
    this.paintEdges()
  }

  /**
   * 清除在 selected 高亮链路省的节点和边 state
   * @param allNode boolean 是否全部节点
   */
  public clearOnSelectedHighlightLinkStates(allNode: boolean) {
    // 节点
    if (allNode) {
      const allNodes: any[] = this._graph.getNodes()
      allNodes.forEach((node: any) => {
        const historyStates: any = node.getProp('states')
        node.setProp('states', {
          ...historyStates,
          selected: false,
          haveHighlightLink: false,
          onSelectedHighlightLink: false,
          onSelectedHighlightLinkField: [],
        })
      })
    } else if (this._onSelectedHighlightLinkNodes.length > 0) {
      const statesCallback: any = ({ historyStates }: any) => {
        return {
          ...historyStates,
          selected: false,
          haveHighlightLink: false,
          onSelectedHighlightLink: false,
          onSelectedHighlightLinkField: [],
        }
      }
      this.updateNodesOnSelectedHighlightLinkStates(
        this._onSelectedHighlightLinkNodes,
        statesCallback
      )
    }
    // 边
    if (this._onSelectedHighlightLinkEdges.length > 0) {
      this.updateEdgesOnSelectedHighlightLinkStates(
        this._onSelectedHighlightLinkEdges,
        false
      )
    }
  }

  /**
   * 更新在 selected 高亮链路上的 node states
   * @param nodes
   * @param statesCallback
   */
  public updateNodesOnSelectedHighlightLinkStates(
    nodes: Array<{ id: string; fieldName: string }>,
    statesCallback: any
  ) {
    const allNodes: any[] = this._graph.getNodes()
    allNodes.forEach((node: any) => {
      const historyStates: any = node.getProp('states')
      const states: any = statesCallback({ node, historyStates })
      node.setProp('states', states)
    })
  }

  /**
   * 更新在 selected 高亮链路上的 edge states
   * @param edges
   * @param value
   */
  public updateEdgesOnSelectedHighlightLinkStates(edges: any, value: boolean) {
    this.updateEdgesStates(edges, 'onSelectedHighlightLink', value)
  }

  /**
   * ------------------ 更新 edges states start ------------------
   */

  /**
   * 更新 edge 指定 state
   * @param edges
   * @param stateKey
   * @param value
   */
  public updateEdgesStates(edges: any, stateKey: string, value: boolean) {
    if (edges?.length > 0) {
      edges.forEach((edge: any) => {
        const currentEdge: any =
          typeof edge === 'string' ? this._graph.getCellById(edge) : edge
        currentEdge.setPropByPath(`states/${stateKey}`, value)
      })
    }
  }

  /**
   * ------------------ edge 着色 start ------------------
   */

  /**
   * 多个 edges 着色
   * @param edges
   */
  public paintEdges(edges?: any) {
    const paintEdges: any = edges || this._graph.getEdges()
    paintEdges.forEach((edge: any) => {
      this.paintEdge(edge)
    })
  }

  /**
   * 指定边 着色
   * @param edge
   */
  public paintEdge(edge: any) {
    const currentEdge: any =
      typeof edge === 'string' ? this._graph.getCellById(edge) : edge
    const selected: boolean = currentEdge.getPropByPath('states/selected')
    const hover: boolean = currentEdge.getPropByPath('states/hover')
    const onSelectedHighlightLink: boolean = currentEdge.getPropByPath(
      'states/onSelectedHighlightLink'
    )
    const onHoverHighlightLink: boolean = currentEdge.getPropByPath(
      'states/onHoverHighlightLink'
    )
    let color: string
    let zIndex: number = 2
    if (hover) {
      color = currentEdge.getPropByPath('states/hoverHighlightColor')
    } else if (selected) {
      color = currentEdge.getPropByPath('states/selectedHighlightColor')
    } else if (onSelectedHighlightLink) {
      color = currentEdge.getPropByPath('states/selectedLinkHighlightColor')
    } else if (onHoverHighlightLink) {
      color = currentEdge.getPropByPath('states/hoverLinkHighlightColor')
    } else {
      color = currentEdge.getPropByPath('states/baseColor')
      zIndex = 1
    }
    currentEdge.attr('line/stroke', color)
    currentEdge.setZIndex(zIndex)
  }

  /**
   * ------------------ hover node start ------------------
   */

  /**
   * hover 节点
   */
  public hoverNode(nodeInfo: NodeInfo) {
    const { nodeId, fieldName } = nodeInfo
    this.clearOnHoverHighlightLinkStates(true)
    const { linkNodes, linkEdges } = this.searchLinkNodesAndEdgesByNodeId(
      nodeId,
      'ALL',
      true,
      fieldName
    )
    this._hoverNode = nodeInfo
    this._onHoverHighlightLinkNodes = linkNodes
    this._onHoverHighlightLinkEdges = linkEdges
    const linkNodesMap: any = arrayToObjectByKey(linkNodes, 'id', 'fieldName')
    const statesCallback: any = ({ node, historyStates }: any) => {
      const addFieldNames = linkNodesMap[node.id] || []
      return {
        ...historyStates,
        haveHighlightLink: linkNodes.length > 0,
        onHoverHighlightLink: !!linkNodesMap[node.id],
        onHoverHighlightLinkField: compact([
          ...addFieldNames,
          ...(historyStates.onHoverHighlightLinkField || []),
        ]),
      }
    }
    this.updateNodesOnHoverHighlightLinkStates(linkNodes, statesCallback)
    this.updateEdgesOnHoverHighlightLinkStates(linkEdges, true)
    this.paintEdges()
  }

  /**
   * 清除 hover 高亮链路上的节点&边状态
   * @param allNode 是否所有节点
   */
  public clearOnHoverHighlightLinkStates(allNode: boolean = false) {
    if (allNode) {
      const allNodes: any[] = this._graph.getNodes()
      allNodes.forEach((node: any) => {
        const historyStates: any = node.getProp('states')
        node.setProp('states', {
          ...historyStates,
          haveHighlightLink: this._onSelectedHighlightLinkNodes.length > 0,
          onHoverHighlightLink: false,
          onHoverHighlightLinkField: [],
        })
      })
    } else if (this._onHoverHighlightLinkNodes.length > 0) {
      const statesCallback: any = ({ historyStates }: any) => {
        return {
          ...historyStates,
          haveHighlightLink: this._onSelectedHighlightLinkNodes.length > 0,
          onHoverHighlightLink: false,
          onHoverHighlightLinkField: [],
        }
      }
      this.updateNodesOnHoverHighlightLinkStates(
        this._onHoverHighlightLinkNodes,
        statesCallback
      )
    }
    // 边
    if (this._onHoverHighlightLinkEdges.length > 0) {
      this.updateEdgesOnHoverHighlightLinkStates(
        this._onHoverHighlightLinkEdges,
        false
      )
    }
    this._onHoverHighlightLinkNodes = []
    this._onHoverHighlightLinkEdges = []
  }

  /**
   * 更新在 selected 高亮链路上的 node states
   * @param nodes
   * @param statesCallback
   */
  public updateNodesOnHoverHighlightLinkStates(
    nodes: Array<{ id: string; fieldName: string }>,
    statesCallback: any
  ) {
    const allNodes: any[] = this._graph.getNodes()
    allNodes.forEach((node: any) => {
      const historyStates: any = node.getProp('states')
      const states: any = statesCallback({ node, historyStates })
      node.setProp('states', states)
    })
  }

  /**
   * 更新在 hover 高亮链路上的 edge states
   * @param edges
   * @param value
   */
  public updateEdgesOnHoverHighlightLinkStates(edges: any, value: boolean) {
    this.updateEdgesStates(edges, 'onHoverHighlightLink', value)
  }

  /**
   * ------------------ select 边 start ------------------
   */

  public selectEdge(edge: any) {
    const currentEdge: any =
      typeof edge === 'string' ? this._graph.getCellById(edge) : edge
    const selectEdgeId: string =
      typeof edge === 'string' ? edge : currentEdge?.id
    if (!edge || this._selectedEdgeId === selectEdgeId) {
      if (this._selectedEdgeId) {
        this.updateEdgesStates([this._selectedEdgeId], 'selected', false)
        this.paintEdge(this._selectedEdgeId)
      }
      this._selectedEdgeId = null
      return
    }
    // 取消已经选择的边
    if (this._selectedEdgeId) {
      this.updateEdgesStates([this._selectedEdgeId], 'selected', false)
      this.paintEdge(this._selectedEdgeId)
    }
    this._selectedEdgeId = selectEdgeId
    this.updateEdgesStates([currentEdge], 'selected', true)
    this.paintEdge(currentEdge)
  }

  /**
   * ------------------ hover 边 start ------------------
   */

  public hoverEdge(edge: any) {
    const id: string = typeof edge === 'string' ? edge : edge.id
    this.updateEdgesStates([id], 'hover', true)
    this.paintEdge(id)
  }

  public hoverLeaveEdge(edge: any) {
    const id: string = typeof edge === 'string' ? edge : edge.id
    this.updateEdgesStates([id], 'hover', false)
    this.paintEdge(id)
  }
}
